Skip to content

Passkeys web wallet#479

Open
maycon-mello wants to merge 17 commits intomasterfrom
passkeys-web-wallet
Open

Passkeys web wallet#479
maycon-mello wants to merge 17 commits intomasterfrom
passkeys-web-wallet

Conversation

@maycon-mello
Copy link
Copy Markdown
Collaborator

No description provided.

@maycon-mello maycon-mello force-pushed the passkeys-web-wallet branch from 2019024 to ce8045a Compare April 3, 2026 19:05
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds passkey-based authentication to the Truvera Web Wallet SDK, enabling WebAuthn PRF-derived key material to encrypt/decrypt the cloud wallet master key, with automatic enrollment + localStorage persistence on first use.

Changes:

  • Introduces low-level WebAuthn passkey helpers (register, PRF assertion, credentialId encoding).
  • Extends web initialize() to support passkey: true | { ...options }, including enrollment and subsequent authentication.
  • Adds core cloud-wallet passkey key-derivation + enrollment/authentication helpers and updates docs/examples/tests.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
packages/web/src/passkey.js New WebAuthn/PRF helper utilities (registration, PRF extraction, credentialId encode/decode).
packages/web/src/passkey.test.js Unit tests for passkey helpers.
packages/web/src/index.js Adds passkey flow to SDK initialization, plus exported passkey helpers.
packages/web/src/index.test.js Adds initialization tests for passkey enroll/auth flows and validation changes.
packages/web/README.md Documents passkey auth usage/options and mnemonic return behavior.
packages/web/example.html Updates mnemonic example to use local bundle and logs output.
packages/web/example-passkey.html Adds passkey example HTML page.
packages/core/src/cloud-wallet.ts Adds passkey-based vault key derivation + enroll/auth/init APIs.
packages/core/src/cloud-wallet.test.js Adds tests for new core passkey APIs.
docs/cloud-wallet.md Documents passkey authentication architecture and usage patterns.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* @throws {Error} If WebAuthn is not supported or user cancels
*/
export async function registerPasskey(identifier, rpName, rpId) {
if (!window.PublicKeyCredential) {
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

registerPasskey references window directly (window.PublicKeyCredential / window.location.hostname). If this function is called in a non-browser/SSR context where window is undefined, it will throw a ReferenceError before the intended “WebAuthn is not supported” error. Use the same typeof window === 'undefined' guard as checkPasskeySupport() and throw a clear error message when not in a browser.

Suggested change
if (!window.PublicKeyCredential) {
if (typeof window === 'undefined' || !window.PublicKeyCredential) {

Copilot uses AI. Check for mistakes.
Comment on lines +154 to +155
if (!window.PublicKeyCredential) {
throw new Error('WebAuthn is not supported in this browser');
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getPasskeyPRFKey also references window directly. In non-browser/SSR contexts this will throw a ReferenceError instead of a controlled failure. Add a typeof window === 'undefined' (and navigator?.credentials) guard and surface a clear error when WebAuthn APIs are unavailable.

Suggested change
if (!window.PublicKeyCredential) {
throw new Error('WebAuthn is not supported in this browser');
if (
typeof window === 'undefined' ||
typeof navigator === 'undefined' ||
!window.PublicKeyCredential ||
!navigator.credentials
) {
throw new Error(
'WebAuthn APIs are unavailable in this environment. ' +
'Passkey operations require a browser with PublicKeyCredential and navigator.credentials support.',
);

Copilot uses AI. Check for mistakes.
Comment on lines +238 to +244
localStorage.setItem(
resolved.storageKey,
JSON.stringify({
passkeyCredentialId: credentialIdBase64url,
identifier: resolved.identifier,
}),
);
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enrollPasskey writes enrollment metadata to localStorage without handling storage failures (e.g., Safari private mode, quota exceeded, storage disabled). This can cause passkey enrollment to succeed but initialization to hard-fail when localStorage.setItem throws. Wrap the write in a try/catch and either (a) proceed without persistence (forcing re-enrollment next time) or (b) throw a dedicated error explaining that persistent storage is required for passkey mode.

Suggested change
localStorage.setItem(
resolved.storageKey,
JSON.stringify({
passkeyCredentialId: credentialIdBase64url,
identifier: resolved.identifier,
}),
);
try {
localStorage.setItem(
resolved.storageKey,
JSON.stringify({
passkeyCredentialId: credentialIdBase64url,
identifier: resolved.identifier,
}),
);
} catch (_error) {
// Ignore storage failures (for example Safari private browsing, quota exceeded,
// or disabled storage). Enrollment has already succeeded; callers can still use
// the returned passkeyCredentialId for direct authentication on subsequent loads.
}

Copilot uses AI. Check for mistakes.
Comment on lines +323 to +328
// Use the identifier from enrollment to ensure PRF salt consistency
if (
!resolved.identifier ||
resolved.identifier === resolvePasskeyOptions(true).identifier
) {
identifier = stored.identifier || identifier;
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comparison calls resolvePasskeyOptions(true) just to retrieve the default identifier. In non-browser environments (no window.location.hostname), resolvePasskeyOptions(true) throws, which can break passkey initialization even when a valid identifier was provided. Avoid calling resolvePasskeyOptions(true) here; instead compare against the computed default identifier/hostname that’s already available in scope.

Copilot uses AI. Check for mistakes.
Comment on lines +332 to +334
const prfOptions = passkeyCredentialId
? {credentialId: base64urlToCredentialId(passkeyCredentialId), rpId}
: {rpId};
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

base64urlToCredentialId(passkeyCredentialId) will throw (via atob) with a low-level/cryptic message if the provided passkeyCredentialId is malformed. Since this is user-supplied config, consider validating/catching decode errors here and rethrowing a clear Initialization failed: invalid passkeyCredentialId style error so callers can diagnose configuration issues.

Suggested change
const prfOptions = passkeyCredentialId
? {credentialId: base64urlToCredentialId(passkeyCredentialId), rpId}
: {rpId};
let prfOptions;
if (passkeyCredentialId) {
try {
prfOptions = {
credentialId: base64urlToCredentialId(passkeyCredentialId),
rpId,
};
} catch {
throw new Error(
'Initialization failed: invalid passkeyCredentialId',
);
}
} else {
prfOptions = {rpId};
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants